#include "microprocesador.h"
#include "aritmetica_binaria.h"

Microprocesador::Microprocesador(Memoria &memoria):
memoria_(memoria),
decodificador_(memoria)
{
	for (int i = 0; i < cantidad_registros; i++) {
		registro_[i] = 0;
	}
	
	sp_ = 0;
	pc_ = 0;
	

	halt_ = false;

	instruccion_[MOV] = &Microprocesador::mov;
	instruccion_[MVI] = &Microprocesador::mvi;
	instruccion_[LXI] = &Microprocesador::lxi;
	instruccion_[LDA] = &Microprocesador::lda;
	instruccion_[STA] = &Microprocesador::sta;
/*	instruccion_[LHLD] = &Microprocesador::lhld;
	instruccion_[SHLD] = &Microprocesador::shld;
	instruccion_[LDAX] = &Microprocesador::ldax;
	instruccion_[STAX] = &Microprocesador::stax;
	instruccion_[XCHG] = &Microprocesador::xchg;

	instruccion_[ADD] = &Microprocesador::add;
	instruccion_[ADI] = &Microprocesador::adi;
	instruccion_[ADC] = &Microprocesador::adc;
	instruccion_[ACI] = &Microprocesador::aci;
	instruccion_[SUB] = &Microprocesador::sub;
	instruccion_[SUI] = &Microprocesador::sui;
	instruccion_[SBB] = &Microprocesador::sbb;
	instruccion_[SBI] = &Microprocesador::sbi;
	instruccion_[INR] = &Microprocesador::inr;
	instruccion_[DCR] = &Microprocesador::dcr;
	instruccion_[INX] = &Microprocesador::inx;
	instruccion_[DCX] = &Microprocesador::dcx;
	instruccion_[DAD] = &Microprocesador::dad;
	instruccion_[DAA] = &Microprocesador::daa;

	instruccion_[ANA] = &Microprocesador::ana;
	instruccion_[ANI] = &Microprocesador::ani;
	instruccion_[XRA] = &Microprocesador::xra;
	instruccion_[XRI] = &Microprocesador::xri;
	instruccion_[ORA] = &Microprocesador::ora;
	instruccion_[ORI] = &Microprocesador::ori;
	instruccion_[CMP] = &Microprocesador::cmp;
	instruccion_[CPI] = &Microprocesador::cpi;
	instruccion_[RLC] = &Microprocesador::rlc;
	instruccion_[RRC] = &Microprocesador::rrc;
	instruccion_[RAL] = &Microprocesador::ral;
	instruccion_[RAR] = &Microprocesador::rar;
	instruccion_[CMA] = &Microprocesador::cma;
	instruccion_[CMC] = &Microprocesador::cmc;
	instruccion_[STC] = &Microprocesador::stc;


	instruccion_[JMP] = &Microprocesador::jmp;
	instruccion_[JCOND] = &Microprocesador::jcond;
	instruccion_[CALL] = &Microprocesador::call;
	instruccion_[CCOND] = &Microprocesador::ccond;
	instruccion_[RET] = &Microprocesador::ret;
	instruccion_[RCOND] = &Microprocesador::rcond;
	instruccion_[RST] = &Microprocesador::rst;
	instruccion_[PCHL] = &Microprocesador::pchl;

	instruccion_[PUSH] = &Microprocesador::push;
	instruccion_[POP] = &Microprocesador::pop;
	instruccion_[XTHL] = &Microprocesador::xthl;
	instruccion_[SPHL] = &Microprocesador::sphl;
	instruccion_[IN] = &Microprocesador::in;
	instruccion_[OUT] = &Microprocesador::out;
	instruccion_[EI] = &Microprocesador::ei;
	instruccion_[DI] = &Microprocesador::di;
	instruccion_[HLT] = &Microprocesador::hlt;
	instruccion_[NOP] = &Microprocesador::nop;
	instruccion_[RIM] = &Microprocesador::rim;
	instruccion_[SIM] = &Microprocesador::sim;
*/
}

Microprocesador::~Microprocesador()
{

}

//TODO: faltan implementar accesores y modificadores
void Microprocesador::setRegistro(Registro registro, byte dato)
{
	registro_[registro] = dato;
}


void Microprocesador::setParDeRegistros(ParDeRegistros registro, word dato)
{
	switch (registro) {
		case PAR_BC:
			registro_[REGISTRO_B] = dato >> 8; 
			registro_[REGISTRO_C] = dato & 0x00ff;
			break;
		case PAR_DE:
			registro_[REGISTRO_D] = dato >> 8; 
			registro_[REGISTRO_E] = dato & 0x00ff;
			break;
		case PAR_HL:
			registro_[REGISTRO_H] = dato >> 8; 
			registro_[REGISTRO_L] = dato & 0x00ff;
			break;
		case PAR_SP:
			sp_ = dato;
			break;
		case PAR_PC:
			pc_ = dato;
		default:
			break;
	}
}


void Microprocesador::setBandera(Bandera bandera, bool dato)
{
	byte mascara;

	mascara = dato ? 1 : 0;

	switch (bandera) {
		case BANDERA_P:
			mascara <<= 2;
			break;
		case BANDERA_A:
			mascara <<= 4;
			break;
		case BANDERA_Z:
			mascara <<= 6;
			break;
		case BANDERA_S:
			mascara <<= 7;
			break;
		default:
			break;
	}

	if (dato) {
		registro_[REGISTRO_PSW] |= mascara;
	} else {
		registro_[REGISTRO_PSW] &= ~mascara;
	}

}


byte Microprocesador::getRegistro(Registro registro) const
{
	return registro_[registro];
}


word Microprocesador::getParDeRegistros(ParDeRegistros registro) const
{
	word dato = -1;

	switch (registro) {
		case PAR_BC:
			dato = registro_[REGISTRO_B] << 8;
			dato |= registro_[REGISTRO_C];
			break;
		case PAR_DE:
			dato = registro_[REGISTRO_D] << 8;
			dato |= registro_[REGISTRO_E];
			break;
		case PAR_HL:
			dato = registro_[REGISTRO_H] << 8;
			dato |= registro_[REGISTRO_L];
			break;
		case PAR_SP:
			dato = sp_;
			break;
		case PAR_PC:
			dato = pc_;
			break;
		default:
			break;
	}

	return dato;
}

bool Microprocesador::getBandera(Bandera bandera) const
{
	bool dato = 0;

	switch (bandera) {
		case BANDERA_C:
			dato = registro_[REGISTRO_PSW] & 0x01;
		case BANDERA_P:
			dato = registro_[REGISTRO_PSW] & 0x04;
		case BANDERA_A:
			dato = registro_[REGISTRO_PSW] & 0x010;
		case BANDERA_Z:
			dato = registro_[REGISTRO_PSW] & 0x40;
		case BANDERA_S:
			dato = registro_[REGISTRO_PSW] & 0x80;
		default:
			break;
	}

	return dato;
}

Memoria& Microprocesador::getMemoria()
{
	return memoria_;
}


void Microprocesador::ejecutar()
{
	Instruccion instruccion;

	decodificador_.decodificar(pc_);
	instruccion = decodificador_.getInstruccion();

	// Una línea un poco feita
	(this->*instruccion_[instruccion.getTipo()])(instruccion);
}




// Implementación de instrucciones

void Microprocesador::mov(const Instruccion &instruccion)
{
	byte dato;
	Registro origen, destino;

	//TODO:se permite hacer MOV, M,M, eliminar ese  caso y levantar
	// una excepción

	origen = instruccion.getDestino();
	destino = instruccion.getDestino();


	if (origen == REGISTRO_M) {
		dato = memoria_[getParDeRegistros(PAR_HL)];
	} else {
		dato = registro_[origen];
	}

	if (destino == REGISTRO_M) {
		memoria_[getParDeRegistros(PAR_HL)] = dato;
	} else {
		registro_[destino] = dato;
	}

	pc_ += instruccion.getLargo();
}

void Microprocesador::mvi(const Instruccion &instruccion)
{
	byte dato;
	Registro registro;


	registro = instruccion.getRegistro();

	dato = instruccion.getValorInmediato8();

	if (registro == REGISTRO_M) {
		memoria_[getParDeRegistros(PAR_HL)] = dato;
	} else {
		registro_[registro] = dato;
	}

	pc_ += instruccion.getLargo();	
}

void Microprocesador::lxi(const Instruccion &instruccion)
{
	setParDeRegistros(instruccion.getParDeRegistros(), instruccion.getValorInmediato16());
	pc_ += instruccion.getLargo();
}

void Microprocesador::lda(const Instruccion &instruccion)
{
	registro_[REGISTRO_A] = memoria_[instruccion.getValorInmediato8()];
	pc_ += instruccion.getLargo();
}

void Microprocesador::sta(const Instruccion &instruccion)
{
	memoria_[instruccion.getValorInmediato8()] = registro_[REGISTRO_A];
	pc_ += instruccion.getLargo();
}

void Microprocesador::lhld(const Instruccion &instruccion)
{

	//TODO:preguntarle a pablo si la direcion de 16-bits la guarda en inmediato8
	// o piensa usar otro campo
//	registro_[REGISTRO_L] = memoria_[];
	pc_ += instruccion.getLargo();
}

void Microprocesador::shld(const Instruccion &instruccion)
{
	//En el mismo estado que lhld
}

void Microprocesador::ldax(const Instruccion &instruccion)
{
	ParDeRegistros registro = instruccion.getParDeRegistros();

	if (registro == PAR_BC || registro == PAR_DE) {
		registro_[REGISTRO_A] = memoria_[registro];
		pc_ += instruccion.getLargo();
	} else {
		//Se debería levantar una exepción
	}
}

void Microprocesador::stax(const Instruccion &instruccion)
{
	ParDeRegistros registro = instruccion.getParDeRegistros();

	if (registro == PAR_BC || registro == PAR_DE) {
		 memoria_[registro] = registro_[REGISTRO_A];
		pc_ += instruccion.getLargo();
	} else {
		//Se debería levantar una exepción
	}
}

void Microprocesador::xchg(const Instruccion &instruccion)
{
	word aux;

	aux = getParDeRegistros(PAR_HL);
	setParDeRegistros(PAR_HL, getParDeRegistros(PAR_DE));
	setParDeRegistros(PAR_DE, aux);
	pc_ += instruccion.getLargo();
}

void Microprocesador::add(const Instruccion &instruccion)
{
	byte dato, resultado, acc;
	Registro registro = instruccion.getRegistro();
	
	if (registro == REGISTRO_M) {
		dato = memoria_[getParDeRegistros(PAR_HL)];
	} else {
		dato = registro_[registro];
	}

	acc = registro_[REGISTRO_A];
	resultado = acc + dato;
	registro_[REGISTRO_A] = resultado;


	setBandera(BANDERA_S, signo(resultado));
	setBandera(BANDERA_Z, cero(resultado));
	setBandera(BANDERA_A, acarreoAuxiliar(acc, dato));
	setBandera(BANDERA_P, paridad(resultado));
	setBandera(BANDERA_C,  acarreo(acc, dato));

	pc_ += instruccion.getLargo();
}

